﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using System.Text.RegularExpressions;
using System.Windows.Documents;
using System.Windows.Markup;
using System.Windows.Media;
using System.IO;

namespace iTFF.WPF.Model.Wikipedia
{
    class WikipediaParser
    {

        // boxes
        Regex _addinitionalInformations = new Regex(@"\s*?\{\{.*?\}\}\s*?", RegexOptions.Singleline);
        Regex _tables = new Regex(@"\{\|.*?\|\}", RegexOptions.Singleline);
        Regex _tags = new Regex("<.*?>.*?</.*?>", RegexOptions.Singleline);

        // lists
        Regex _listsRE = new Regex(@"((\*\s*.+?\n)+)");
        string _listsReplace = "\n<List>\n$1</List>\n\n";
        Regex _listItemsRE = new Regex(@"\*\s*(?<line>.+?)\s*?\n");
        string _listItemsReplace = "\t<ListItem><Paragraph>${line}</Paragraph></ListItem>\n";

        // visual
        string _headingRE = @"^\s*{0}\s*(?<text>.*?)\s*{0}\s*$";

        Regex _h1;
        string _h1Replace = "<Paragraph Tag=\"h1\">${text}</Paragraph>\n";
        Regex _h2;
        string _h2Replace = "<Paragraph Tag=\"h2\">${text}</Paragraph>\n";
        Regex _h3;
        string _h3Replace = "<Paragraph Tag=\"h3\">${text}</Paragraph>\n";
        Regex _h4;
        string _h4Replace = "<Paragraph Tag=\"h4\">${text}</Paragraph>\n";
        Regex _h5;
        string _h5Replace = "<Paragraph Tag=\"h5\">${text}</Paragraph>\n";
        Regex _h6;
        string _h6Replace = "<Paragraph Tag=\"h6\">${text}</Paragraph>\n";

        Regex _definitionList = new Regex(@"^\s*;\s*(?<text>.*?)\s*$", RegexOptions.Multiline);
        string _definitionListReplace = "<Paragraph Tag=\"h3\">${text}</Paragraph>\n";

        // links
        Regex _linkLang = new Regex(@"\[\[[a-z]{2}\:(?<text>.+?)\]\]");
        Regex _link = new Regex(@"\[\[((?<link>[^\]]*?)\|)?(?<text>.+?)\]\]");
        string _linkReplace = "<Hyperlink NavigateUri=\"{0}\">{1}</Hyperlink>";

        // images
        Regex _linkImage = new Regex(@"\[\[Image:(?<text>.+)]\]", RegexOptions.IgnoreCase);
        Dictionary<string, System.Windows.FigureHorizontalAnchor> _imagePositions;
        Regex _linkImageSize = new Regex("(?<width>[0-9]+)px((?<height>[0-9]+)px)?", RegexOptions.IgnoreCase);

        // font
        Regex _bold = new Regex("'''(?<text>.*?)'''");
        string _boldReplace = "<Bold>${text}</Bold>";
        Regex _italic = new Regex("''(?<text>.*?)''");
        string _italicReplace = "<Italic>${text}</Italic>";

        // paragraphs
        Regex _paragraphs = new Regex(@"(^\s*(?<content>.+?)\n{1,})", RegexOptions.Multiline);
        string[] _paragraphsEscapes = new string[] { "<ListItem", "</ListItem", "<List", "</List", "<Paragraph", "</Paragraph" };
        string _paragraphReplace = "<Paragraph>\n\t{0}\n</Paragraph>\n";

        // replacement 
        new Dictionary<string, string> _documentReplacements;

        public WikipediaParser()
        {
            _h1 = new Regex(string.Format(_headingRE, "="), RegexOptions.Multiline);
            _h2 = new Regex(string.Format(_headingRE, "=="), RegexOptions.Multiline);
            _h3 = new Regex(string.Format(_headingRE, "==="), RegexOptions.Multiline);
            _h4 = new Regex(string.Format(_headingRE, "===="), RegexOptions.Multiline);
            _h5 = new Regex(string.Format(_headingRE, "====="), RegexOptions.Multiline);
            _h6 = new Regex(string.Format(_headingRE, "======"), RegexOptions.Multiline);

            _imagePositions = new Dictionary<string, System.Windows.FigureHorizontalAnchor>() 
            { 
                { "left", System.Windows.FigureHorizontalAnchor.ColumnLeft },
                { "right", System.Windows.FigureHorizontalAnchor.ColumnRight },
                { "center", System.Windows.FigureHorizontalAnchor.ColumnCenter },
            };

           _documentReplacements = new Dictionary<string, string>() { { "&", "&amp;" } };

        }

        /// <summary>
        /// Gets the export part from an API request result
        /// </summary>
        /// <param name="export"></param>
        /// <returns></returns>
        public string GetAPIResultExportPart(string export)
        {
            XDocument doc = XDocument.Parse(export);

            var content = doc.Element("api").Element("query").Element("export");
            return content.Value;

        }

        /// <summary>
        /// Parses a Wikipedia export
        /// </summary>
        /// <param name="export"></param>
        /// <param name="baseURL"></param>
        /// <returns></returns>
        public WikipediaParseResult ParseXMLExportToFlowDocumentXaml(string export, string baseURL)
        {
            WikipediaParseResult result = new WikipediaParseResult();

            XDocument doc = XDocument.Parse(export);

            var ns = XNamespace.Get("http://www.mediawiki.org/xml/export-0.3/");

            var content = doc.Element(ns + "mediawiki").Element(ns + "page")
                .Element(ns + "revision").Element(ns + "text").Value;

            // remove the boxes
            content = _addinitionalInformations.Replace(content, "");
            content = _tables.Replace(content, "");
            content = _tags.Replace(content, "");

            // remove lang links
            content = _linkLang.Replace(content, "");

            // replace for example & by &amp;
            //content = System.Web.HttpUtility.HtmlEncode(content);

            content = ParseImages(content, baseURL, result);

            // replace the links
            content = ParseLinks(content, baseURL);

            // replace italics and bold
            content = ParseFontStyles(content);

            // replace the headings
            content = _h6.Replace(content, _h6Replace);
            content = _h5.Replace(content, _h5Replace);
            content = _h4.Replace(content, _h4Replace);
            content = _h3.Replace(content, _h3Replace);
            content = _h2.Replace(content, _h2Replace);
            content = _h1.Replace(content, _h1Replace);

            content = _definitionList.Replace(content, _definitionListReplace);

            // parse the lists
            content = _listsRE.Replace(content, _listsReplace);
            content = _listItemsRE.Replace(content, _listItemsReplace);

            // add a paragraph to all alone ones
            var p = _paragraphs.Matches(content);

            int shift = 0;
            foreach (Match match in p)
            {
                if (match.Length > 0 && !_paragraphsEscapes.Any(e => match.Groups["content"].Value.StartsWith(e)))
                {
                    var replace = string.Format(_paragraphReplace, match.Value.Trim());

                    content = content.Substring(0, match.Index + shift) + replace + content.Substring(match.Index + shift + match.Length);
                    shift += replace.Length - match.Length;
                }
            }

            // apply replacements
            foreach (var r in _documentReplacements)
                content = content.Replace(r.Key, r.Value);

            result.Xaml = content;

            return result;
        }

        /// <summary>
        /// Parses the font styles (bold & italic)
        /// </summary>
        /// <param name="content"></param>
        /// <returns></returns>
        private string ParseFontStyles(string content)
        {
            content = _bold.Replace(content, _boldReplace);
            content = _italic.Replace(content, _italicReplace);
            return content;
        }

        /// <summary>
        /// Parses the [[Image:]] into the string
        /// </summary>
        /// <param name="content"></param>
        /// <param name="baseURL"></param>
        /// <returns></returns>
        private string ParseImages(string content, string baseURL, WikipediaParseResult result)
        {

            // replace the link images
            var immatch = _linkImage.Match(content);

            //     [[Image:{name}|{type}|{location}|{size}|{upright}|{text}]] 
            while (immatch.Success)
            {

                // split the parts
                var parts = immatch.Groups["text"].Value.Split('|');

                // create the visual elements
                Figure figure = new Figure();

                Hyperlink hyperlink = new Hyperlink();
                figure.Blocks.Add(new Paragraph(hyperlink));

                System.Windows.Controls.Image image = new System.Windows.Controls.Image();
                hyperlink.Inlines.Add(image);

                Paragraph paragraph = new Paragraph();
                figure.Blocks.Add(paragraph);

                if (parts.Length > 0)
                {
                    var wikiImage = new WikipediaImage();

                    // the image name and path
                    var name = parts[0];
                    image.Tag = name;
                    wikiImage.Name = name;

                    var args = parts.Skip(1);

                    bool canSetText = false;
                    bool canResize = false;

                    // type : thumb, thumbnail, frame, border 
                    var typeValues = new string[] { "thumb", "thumbnail", "frame", "border" };
                    if (args.Select(a => a.ToLower()).Intersect(typeValues).Any())
                    {
                        if (args.Contains("thumb") || args.Contains("thumbnail"))
                        {
                            // add a border, can set the text, can resize
                            paragraph.BorderThickness = new System.Windows.Thickness(1);
                            paragraph.BorderBrush = System.Windows.Media.Brushes.Gray;
                            paragraph.Background = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#F9F9F9"));
                            canSetText = true;
                            canResize = true;
                        }
                        else if (args.Contains("frame"))
                        {
                            // add a border, can set the text, can't resize
                            paragraph.BorderThickness = new System.Windows.Thickness(1);
                            paragraph.BorderBrush = System.Windows.Media.Brushes.Gray;
                            paragraph.Background = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#F9F9F9"));
                            canSetText = true;
                        }
                        else if (args.Contains("border"))
                        {
                            // add a border, can't set the text, can't resize
                            paragraph.BorderThickness = new System.Windows.Thickness(1);
                            paragraph.BorderBrush = System.Windows.Media.Brushes.Gray;
                            paragraph.Background = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#F9F9F9"));
                        }

                        // now exclude theese args from selection
                        args = args.Where(a => !typeValues.Contains(a.ToLower()));
                    }

                    // location : right, left, center, none
                    // set the corresponding value or default : right
                    var locationValues = new string[] { "right", "left", "center", "none" };
                    if (args.Select(a => a.ToLower()).Intersect(locationValues).Any())
                    {
                        figure.HorizontalAnchor = _imagePositions[args.Select(a => a.ToLower()).Intersect(locationValues).Last()];
                        // remove from args enumeration
                        args = args.Where(a => !locationValues.Contains(a.ToLower()));
                    }
                    else
                        figure.HorizontalAnchor = System.Windows.FigureHorizontalAnchor.PageRight;

                    // size
                    if (args.Any(a => _linkImageSize.IsMatch(a)))
                    {
                        if (canResize)
                        {
                            var arg = args.Last(a => _linkImageSize.IsMatch(a));
                            var match = _linkImageSize.Match(arg);

                            if (match.Groups["height"].Success)
                            {
                                figure.Height = new System.Windows.FigureLength(double.Parse(match.Groups["height"].Value));
                                wikiImage.Height = int.Parse(match.Groups["height"].Value);
                            }

                            figure.Width = new System.Windows.FigureLength(double.Parse(match.Groups["width"].Value));
                            wikiImage.Width = int.Parse(match.Groups["width"].Value);
                        }

                        // remove from args enumeration
                        args = args.Where(a => !_linkImageSize.IsMatch(a));
                    }

                    // upright : don't take care, just remove it
                    args = args.Where(a => !a.ToLower().StartsWith("upright"));

                    // link : links to an article instead of the image 
                    if (args.Any(a => a.ToLower().StartsWith("link")))
                    {
                        var linkargs = args.Where(a => a.ToLower().StartsWith("link")).Last();

                        var link = linkargs.Split(new char[] { '=' }, 2)[1];
                        string endlinkpart = EncodeLinkEndPart(link);
                        hyperlink.NavigateUri = new Uri(baseURL + endlinkpart);

                        // remove from args enumeration
                        args = args.Where(a => !a.ToLower().StartsWith("link"));
                    }

                    // the text : all left args
                    if (args.Any())
                    {
                        if (canSetText)
                        {
                            string text = string.Join("|", args.ToArray());

                            // parse links and font style
                            //text = ParseLinks(text, baseURL);
                            //text = ParseFontStyles(text);

                            paragraph.Inlines.Add(new Run(text));
                        }
                    }

                    // set link if not set
                    if (hyperlink.NavigateUri == null)
                        hyperlink.NavigateUri = new Uri(baseURL + "File:" + EncodeLinkEndPart(name));

                    // set details url
                    wikiImage.DetailsURL = new Uri(baseURL + "File:" + EncodeLinkEndPart(name));

                    // now replace content, with UTF8 encoding
                    //string replace = string.Empty;
                    //using (MemoryStream stream = new MemoryStream())
                    //using (StreamWriter writer = new StreamWriter(stream, Encoding.UTF8))
                    //{
                    //    XamlWriter.Save(figure, writer);
                    //    stream.Seek(0, SeekOrigin.Begin);
                    //    using (StreamReader reader =new StreamReader(stream, Encoding.UTF8))
                    //    {
                    //        replace = reader.ReadToEnd();
                    //    }
                    //}

                    var replace = XamlWriter.Save(figure);
                    
                    content = _linkImage.Replace(content, replace, 1, immatch.Index);
                    immatch = _linkImage.Match(content, immatch.Index + replace.Length);

                    result.Images.Add(wikiImage);
                }


            }

            return content;

        }

        /// <summary>
        /// Parses the [[links]] into the string
        /// </summary>
        /// <param name="content"></param>
        /// <param name="baseURL"></param>
        /// <returns></returns>
        private string ParseLinks(string content, string baseURL)
        {

            var lmatch = _link.Match(content);
            while (lmatch.Success)
            {
                var linkGroup = lmatch.Groups["link"];
                var textGroup = lmatch.Groups["text"];
                string urlText;

                urlText = linkGroup.Success ? linkGroup.Value : textGroup.Value;

                string replace = string.Format(_linkReplace, baseURL + EncodeLinkEndPart(urlText), textGroup.Value);

                content = _link.Replace(content, replace, 1, lmatch.Index);

                lmatch = _link.Match(content, lmatch.Index + replace.Length);
            }

            return content;

        }

        /// <summary>
        /// Encodes the url to be wikipedia-friendly
        /// </summary>
        /// <param name="link"></param>
        /// <returns></returns>
        public string EncodeLinkEndPart(string link)
        {
            var replacements = new Dictionary<string, string>() { { " ", "_" } };

            foreach (var rep in replacements)
                link = link.Replace(rep.Key, rep.Value);

            return link;
        }

        /// <summary>
        /// Parses the API query result to return the image url part
        /// </summary>
        /// <param name="xmlContent"></param>
        /// <param name="isThumb"></param>
        /// <returns></returns>
        public Uri ParseImageUrl(string xmlContent, bool isThumb)
        {
            XDocument doc = XDocument.Parse(xmlContent);

            var ii = doc.Element("api").Element("query").Element("pages").Element("page").Element("imageinfo").Element("ii");

            return isThumb ? new Uri(ii.Attribute("thumburl").Value) : new Uri(ii.Attribute("url").Value);
        }

        /// <summary>
        /// Replaces images by setting their URI to the image url
        /// </summary>
        /// <param name="results"></param>
        public void ReplaceImagePathes(WikipediaParseResult results)
        {
            foreach (var image in results.Images)
            {

                results.Xaml = results.Xaml.Replace(string.Format("<Image Tag=\"{0}\" />", image.Name),
                    string.Format("<Image Tag=\"{0}\" Source=\"{1}\" />", image.Name, image.FileURL));

            }
        }
    }
}
